--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════════════════════╗
--║     GRIND RAILS SET UP     ║  Grind Rails are made by Golden Shine! The day for rail grinding in SRB2 has finally arrived!   ║
--║════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════║
--║ This lump and LUA_HOOKS work together to allowing loading duplicate Grind Rails mods without creating issues/lag.			 ║
--║ They will automatically override older Grind Rail versions with a newer versions's features!								 ║
--║ 							...I've also decided to put saving and loading here.											 ║
--╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
if GS_RAILS_V==nil print("\x85"+"FATAL ERROR:\x81"+"GS_RAILS_V was somehow not valid!") return end --
local VERSION = GS_RAILS_V  --Our version table. This shit BETTER be loaded.

--Is a newer version of GS Rails already loaded? Don't make duplicates, only use the newest one!
--It checks if we have an old GS_RAILS table and if that table's version is higher than GS_RAILS_V's version.
--If it is, overwrite GS_RAILS' old functions, WITHOUT adding new hooks.(ThinkFrame, PostThinkFrame, etc)
if not (GS_RAILS) or not (GS_RAILS.version) --There's never BEEN a version, so obviously continue.
or (GS_RAILS.version < VERSION.version) --Version is higher than last added one, continue!

	 --This is the main rail table that we'll be using for most of our operations.
	 --It's jam-packed with functions and variables, and also contains the current version number.
	 --By packing all the functions into this one table, it's easy to completely replace functionality in updates.
	if not (GS_RAILS) or type(GS_RAILS)!="table" or not (GS_RAILS.version)
		rawset(_G,"GS_RAILS",{}) --Make the table!
	end
	local RAIL = GS_RAILS
	if GS_RAILS.version==nil
		VERSION.FirstLoad = true
		VERSION.timer = 3
	else
		VERSION.timer = 7
	end
	RAIL.version = VERSION.version --This is now the newest version.
	RAIL.blockload = false --Since this is new, do not block the Lua lumps from loading.
else
	GS_RAILS.blockload = true
end

local RAIL = GS_RAILS --Shorten GS_RAILS to RAIL for convenience! You can simply call GS_RAILS.ActivateRail if preferred. (if GS_RAILS exists)
RAIL.maxhook = (RAIL.maxhook) and 2 or 1 --Only load hooks once, use the actual editable tables to update the behaviour!
RAIL.LastErrorMessage,RAIL.LastMessage,RAIL.LastPrint = nil

if RAIL.blockload return end --Stop here otherwise.

--Global table for Rails Settings. (Currently unused, just forward thinking.)
if GS_RAILS_SETTINGS==nil
	rawset(_G,"GS_RAILS_SETTINGS", {})
end

--The GS_RAILS_SKINS table, allowing coders to create special behavior on characters that will only activates when GS Grind Rails 
--are actually added to SRB2. This saves performance for everyone involved, and allows for a crapton of customizability.
--Most character modders will only need to know how to use the rails version of S_SKIN. Check LUA_CUSTOMCHARS for info on that!
if GS_RAILS_SKINS==nil
	rawset(_G,"GS_RAILS_SKINS",{})
end

--The Rail Director is an object(MT_GSRAILDIRECTOR) that connects all rails in a level, then keeps track of said rails
--using it's own seperate tables. This massively saves performance, and works well for SRB2's netcode. 
--It also means GSGrindRails adds almost zero strain to any levels with no rails them.
--GS_RAILS_DIRECTOR refers to this rail director object once it's spawned. Otherwise it's just an empty table.
if GS_RAILS_DIRECTOR==nil 
	rawset(_G,"GS_RAILS_DIRECTOR", {}) 
end

--This is just a convenience shortcut so you can check if player.powers[pw_carry]==CR_GRINDRAIL, instead of typing 3888.
if (CR_GRINDRAIL!=3888)
	rawset(_G,"CR_GRINDRAIL", 3888)
end

--Error printing. Used to warn of unusual circumstances or if a mapper did something wrong. Sometimes used for debugging.
RAIL.error = function(message)
	if RAIL.LastErrorMessage==message return end --Same message. Don't play.
	print(message)
	if RAIL.LastError==nil or (RAIL.LastError!=leveltime)
		S_StartSound(nil, sfx_skid)
		P_StartQuake(50<<16, 12)
	end
	RAIL.LastErrorMessage = message
	RAIL.LastError = leveltime
end

--╔════════════════════════════╗═════════════════════════════════════════════════════════════════════════════════#
--║     SETTINGS & SAVING      ║    Here are the functions related to saving, loading, and settings.
--╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════#

RAIL.SaveLoad = function(p, dosave, doload, SILENT, SKIN)
	if not (p and p.valid and type(p.GSRO)=="table") --p."Golden Shine's Rail Options"
		return --
	elseif ((isdedicatedserver) and (p==server))
		p.GSRO.hasloaded = true return --
	end
	local FILENAME = "client/GS-GrindRails/GSgrindsettings-v0.3" --Adding .dat manually later.
	local SKINFILE = FILENAME
	
	if doload --We're loading!

		if splitscreen and p==players[1] p.GSRO.hasloaded = true end --You don't get a save!
		if p.GSRO.hasloaded==true return end --Already loaded...
		
		if io
			if (SKIN) and type(SKIN)=="string"
				SKINFILE = $+"["+SKIN+"]"
			elseif p.mo and p.mo.skin and type(p.mo.skin)=="string"
				SKINFILE = $+"["+p.mo.skin+"]"
			end
			SKINFILE = $+".dat"
			local file = io.openlocal(SKINFILE)
			if file==nil --No SKIN-specific file, so load a generic one.
				file = io.openlocal((FILENAME+".dat"))
			/*	if file!=nil and not (modeattacking)
					-- CONS_Printf(p, "Loading global GS Grind Rails save file...!")
				end */
			elseif not (modeattacking) --Found a player specific one!
				-- CONS_Printf(p, "\x83"+"Loading specific GS Grind Rails file for character:\x82 "+p.mo.skin)
			end
			if file!=nil --Found any at all? then load!
				local code = file:read("*a")
				if code ~= nil and (string.find(code, "GSRLoad ")) and not (string.find(code, ";"))
					COM_BufInsertText(p, " "+(tostring(code)))
				end
				file:close()
			end
		end
		return true --
		
	elseif dosave and (p==consoleplayer) and io --We're saving!

		if (SKIN) and type(SKIN)=="string"
			SKINFILE = $+"["+SKIN+"]"
		end
		SKINFILE = $+".dat"
		
		local RO,s1 = p.GSRO,{}

		--Camera stuff.
		s1[1] = (RO.cam_vanilla)	 		and 1 or 0
		s1[2] = (RO.cam_shift==nil) 		and 10 or RO.cam_shift
		s1[3] = (RO.cam_dist==nil)	 		and 192 or RO.cam_dist 
		s1[4] = (RO.cam_height==nil) 		and 40 or RO.cam_height
		s1[5] = (RO.cam_turnspeed==nil)		and 100 or RO.cam_turnspeed
		s1[6] = (RO.cam_autoturnspeed==nil)	and 75 or RO.cam_autoturnspeed
		s1[7] = (RO.cam_centerspeed==nil)	and 150 or RO.cam_centerspeed
		
		--Gameplay stuff	
		s1[8] = (RO.devmode)				and max(1, min(3, RO.devmode)) or 0
		s1[9] = (RO.backupgrindstate) 		and RO.backupgrindstate or SPR2_ROLL 
		s1[10] = (RO.duncegrindstate) 		and RO.duncegrindstate or SPR2_EDGE
		s1[11]=(type(RO.SideHold)=="number")and RO.SideHold or 0
		s1[12] = (RO.HUDaid) 				and RO.HUDaid or 0
	
		--First time or not?	
		s1[13] = (RO.NewbieGrinder==0) 		and 1 or 0
		
		local file,WRITE = io.openlocal(SKINFILE, "w+"), "GSRLoad "
		for i = 1,#s1 WRITE = $+s1[i]+" " end
		file:write(WRITE) file:close()
		if SILENT != 7 and not (modeattacking)
			if (SKIN) and type(SKIN)=="string"
				-- CONS_Printf(p, "\x8b"+"Updated saved settings for\x82"+" GS Grind Rails"+"\x80 ["+"\x88"+SKIN+"\x80"+"]")
			else
				-- CONS_Printf(p, "\x8b"+"Updated global saved settings for\x82"+" GS Grind Rails")
			end
		end
		RO.hasloaded = true return true --
	end
end

--Why'd I make a command to load stuff? Because otherwise the loaded info becomes clientside and desyncs in netgames!
--No, I DON'T know why we have to do it this way either!
COM_AddCommand("GSRLoad", function(p, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11,s12, s13)
	if (gamestate ~= GS_LEVEL) or not (p.valid) return end

	if p==server and (isdedicatedserver)
	or splitscreen==true and p==players[1]
		if p.GSRO p.GSRO.hasloaded = true end
		return --
	end
	local RO = p.GSRO if not (RO) or (RO.hasloaded) return end

	--All of you! Numbers, NOW!
	s1,s2,s3,s4,s5 = tonumber($), tonumber($), tonumber($), tonumber($), tonumber($)
	s6,s7,s8,s9,s10 = tonumber($), tonumber($), tonumber($), tonumber($), tonumber($)
	s11,s12,s13 = tonumber($), tonumber($), tonumber($)

	--Load the camera settings.
	RO.cam_vanilla = (s1==1) and true or false
	if type(s2)=="number"
		RO.cam_shift = s2
	end
	if type(s3)=="number"
		RO.cam_dist = max(10, s3)
	end
	if type(s4)=="number"
		RO.cam_height = max(1, s4)
	end
	if type(s5)=="number"
		RO.cam_turnspeed = max(1, s5)
	end	
	if type(s6)=="number"
		RO.cam_autoturnspeed = max(0, s6)
	end
	if type(s7)=="number"
		RO.cam_centerspeed = max(1, s7)
	end	
	
	--Now load the gameplay stuff.
	if s8 and type(s8)=="number" and (s8 < 4)
		RO.devmode = max(1, s8)
	end
	local SKIN = (p.mo) and p.mo.skin or p.skin
	if SKIN!=nil
		local MAXSPRITES2 = skins[SKIN].sprites
		if MAXSPRITES2
			MAXSPRITES2 = #MAXSPRITES2
			if MAXSPRITES2
				if type(s9)=="number" and (s9 < MAXSPRITES2)
					RO.backupgrindstate = s9
				end
				if type(s10)=="number" and (s10 < MAXSPRITES2)
					RO.duncegrindstate = s10
				end				
			end
		end
	end
	
	--Now load the gameplay stuff.
	if s11 and type(s11)=="number" and (s11 < 5)
		RO.SideHold = max(0, s11)
	end
	
	--Tutorial setting, called HUDaid in the code.
	if s12==0
		RO.HUDaid,RO.NewbieGrinder = 0,0
	elseif not (s12) or s12==1 or s12==true
		RO.HUDaid = 1
	elseif s12==2
		RO.HUDaid,RO.NewbieGrinder = 2,0
	end
	if (modeattacking) or (marathonmode)
		RO.NewbieGrinder = 0 --Not here...!
	end
	
	--Add a console message that we loaded stuff.
	if (RO.hasloaded!=true)
		-- CONS_Printf(p, "\x8b"+"Loaded global"+"\x88 GS GrindRail\x8b settings!")		
		RO.hasloaded = true
	end return true --
end)

--════════════════════════════════════════════DEBUG STUFF BELOW════════════════════════════════════════════════════════

--Checks priority before printing, otherwise don't print.
RAIL.print = function(message, priority) 
	local GO = false
	if type(priority)=="string" --If field 1 is "FORCE", then priority IS the message, and the print will always play.
		if ((string.upper(message))!="FORCE") RAIL.error("use the force, dumbass.") return end
		message,priority,GO = priority, ANGLE_180-1, true
	elseif ((priority or 0) > ANG30) 
		GO = true
	else 
		local p = players[0] local NAME = (p and p.name) and string.upper(p.name) or ""
		if NAME=="GOLDEN SHINE" and (p.cmd.buttons & (BT_CUSTOM1|BT_CUSTOM2|BT_CUSTOM3|BT_TOSSFLAG)) GO = true end
	end
	if GO==true and RAIL.LastPrint!=message
		RAIL.LastPrint = message
		print(message) return true
	end --Done!
end

--Spawns a debug toad on X,Y,Z, or on the object if no coordinates are given. Mimmicks object's color unless FORCECOLOR is set.
--If X is a number, but Y and Z are not, X will be "FORCECOLOR" instead, and it'll spawn on the object given.
--In simpler terms, RAIL.SpawnDebug(mo, SKINCOLOR_BLUE) spawns a blue toad on "mo".
RAIL.SpawnDebug = function(s, X,Y,Z, FORCECOLOR)
	if (type(X)=="number") and Y==nil and Z==nil and FORCECOLOR==nil and type(s)=="userdata"
		FORCECOLOR = X --You can use X as the "FORCECOLOR" field if you want.
		X = nil
	end
	if s and type(s)=="userdata"
		if X==nil X,Y,Z = s.x,s.y,s.z end
	elseif X==nil
		return --Input SOMETHING, tho'...
	end
	
	local DEBUG = P_SpawnMobj(X,Y,Z,MT_TOAD)
	DEBUG.renderflags = RF_FULLBRIGHT|RF_NOCOLORMAPS
	DEBUG.dispoffset = 149 
	DEBUG.flags = $|MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOTHINK|MF_SCENERY
	DEBUG.color,DEBUG.colorized = FORCECOLOR or (s and s.color) and s.color or SKINCOLOR_GOLDENROD, true
	DEBUG.spriteyscale = $+(3<<16)
	if s and s.valid
		DEBUG.scale,DEBUG.angle,DEBUG.tracer = s.scale,s.angle,s
		local LAST = s.GSLastToad 
		if LAST and LAST.valid and LAST.x==DEBUG.x and LAST.y==DEBUG.y and LAST.z==z
		and LAST.color==DEBUG.color and LAST.scale==DEBUG.scale and LAST.angle==DEBUG.angle and LAST.tracer==DEBUG.tracer
			P_RemoveMobj(LAST) --No need for identicals, right?
		end
		s.GSLastToad = DEBUG
	end
end